
/*
 * Copyright 2002-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 *版权所有2002-2017原作者。
 *
 *根据Apache许可证2.0版许可（“许可证”）；
 *除非符合许可证的规定，否则您不得使用此文件。
 *您可以在以下网址获取许可证副本：
 *
 *https://www.apache.org/licenses/LICENSE-2.0
 *
 *除非适用法律要求或书面同意，软件
 *根据许可证进行的分发是在“按原样”的基础上进行的，
 *无任何明示或暗示的保证或条件。
 *有关管理权限的特定语言，请参阅许可证和
 *许可证下的限制。
 */

package org.springframework.validation;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.StringJoiner;

import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;

/**
 * Default implementation of the {@link MessageCodesResolver} interface.
 *
 * <p>Will create two message codes for an object error, in the following order (when
 * using the {@link Format#PREFIX_ERROR_CODE prefixed}
 * {@link #setMessageCodeFormatter(MessageCodeFormatter) formatter}):
 * <ul>
 * <li>1.: code + "." + object name
 * <li>2.: code
 * </ul>
 *
 * <p>Will create four message codes for a field specification, in the following order:
 * <ul>
 * <li>1.: code + "." + object name + "." + field
 * <li>2.: code + "." + field
 * <li>3.: code + "." + field type
 * <li>4.: code
 * </ul>
 *
 * <p>For example, in case of code "typeMismatch", object name "user", field "age":
 * <ul>
 * <li>1. try "typeMismatch.user.age"
 * <li>2. try "typeMismatch.age"
 * <li>3. try "typeMismatch.int"
 * <li>4. try "typeMismatch"
 * </ul>
 *
 * <p>This resolution algorithm thus can be leveraged for example to show
 * specific messages for binding errors like "required" and "typeMismatch":
 * <ul>
 * <li>at the object + field level ("age" field, but only on "user");
 * <li>at the field level (all "age" fields, no matter which object name);
 * <li>or at the general level (all fields, on any object).
 * </ul>
 *
 * <p>In case of array, {@link List} or {@link java.util.Map} properties,
 * both codes for specific elements and for the whole collection are
 * generated. Assuming a field "name" of an array "groups" in object "user":
 * <ul>
 * <li>1. try "typeMismatch.user.groups[0].name"
 * <li>2. try "typeMismatch.user.groups.name"
 * <li>3. try "typeMismatch.groups[0].name"
 * <li>4. try "typeMismatch.groups.name"
 * <li>5. try "typeMismatch.name"
 * <li>6. try "typeMismatch.java.lang.String"
 * <li>7. try "typeMismatch"
 * </ul>
 *
 * <p>By default the {@code errorCode}s will be placed at the beginning of constructed
 * message strings. The {@link #setMessageCodeFormatter(MessageCodeFormatter)
 * messageCodeFormatter} property can be used to specify an alternative concatenation
 * {@link MessageCodeFormatter format}.
 *
 * <p>In order to group all codes into a specific category within your resource bundles,
 * e.g. "validation.typeMismatch.name" instead of the default "typeMismatch.name",
 * consider specifying a {@link #setPrefix prefix} to be applied.
 *
 * @author Juergen Hoeller
 * @author Phillip Webb
 * @author Chris Beams
 * @since 1.0.1
 */
/**
 *｛@link MessageCodesResolver｝接口的默认实现。
 *
 *<p>将按以下顺序为对象错误创建两个消息代码（当
 *使用带有前缀的{@link格式#PREFIX_ERROR_CODE}
 *｛@link#setMessageCodeFormatter（MessageCodeFormater）格式化程序｝）：
 *<ul>
 *<li>1.：代码+“.”+对象名称
 *<li>2.：代码
 *</ul>
 *
 *<p>将按以下顺序为字段规范创建四个消息代码：
 *<ul>
 *＜li＞1.：代码+“.”+对象名称+“.“+字段
 *<li>2.：代码+“.”+字段
 *<li>3.：代码+“.”+字段类型
 *<li>4.：代码
 *</ul>
 *
 *<p>例如，如果代码“typeMismatch”，对象名称“user”，字段“age”：
 *<ul>
 *<li>1。尝试“typeMismatch.user.age”
 *<li>2。试试“typeMismatch.age”
 *<li>3。尝试“typeMismatch.int”
 *<li>4。尝试“类型不匹配”
 *</ul>
 *
 *<p>因此，可以利用此分辨率算法来显示
 *绑定错误的特定消息，如“required”和“typeMismatch”：
 *<ul>
 *<li>在对象+字段级别（“年龄”字段，但仅在“用户”上）；
 *＜li＞在字段级别（所有“年龄”字段，无论哪个对象名称）；
 *＜li＞或在一般级别（所有字段，在任何对象上）。
 *</ul>
 *
 *<p>在数组的情况下，｛@link List｝或｛@linkjava.util.Map｝属性，
 *特定元素和整个集合的代码都是
 *生成。假设对象“user”中的数组“groups”的字段“name”：
 *<ul>
 *<li>1。请尝试“typeMismatch.user.groups[0].name”
 *<li>2。尝试“typeMismatch.user.groups.name”
 *<li>3。请尝试“typeMismatch.groups[0].name”
 *<li>4。尝试“typeMismatch.groups.name”
 *＜li＞5。尝试“typeMismatch.name”
 *<li>6。尝试“typeMismatch.java.lang.String”
 *<li>7。尝试“类型不匹配”
 *</ul>
 *
 *＜p＞默认情况下，｛@code errorCode｝s将被放置在构造的开头
 *消息字符串。｛@link#setMessageCodeFormatter（消息代码格式化程序）
 *messageCodeFormatter｝属性可用于指定替代串联
 *｛@link MessageCodeFormatter格式｝。
 *
 *<p>为了将所有代码分组到资源捆绑包中的特定类别中，
 *例如“validation.typeMismatch.name”而不是默认的“typeMismatch.name”，
 *请考虑指定要应用的{@link#setPrefix前缀}。
 *
 *@作者于尔根·霍勒
 *@作者Phillip Webb
 *@作者Chris Beams
 *@自1.0.1起
 */
@SuppressWarnings("serial")
public class DefaultMessageCodesResolver implements MessageCodesResolver, Serializable {

	/**
	 * The separator that this implementation uses when resolving message codes.
	 */
	/**
	 *此实现在解析消息代码时使用的分隔符。
	 */
	public static final String CODE_SEPARATOR = ".";

	private static final MessageCodeFormatter DEFAULT_FORMATTER = Format.PREFIX_ERROR_CODE;


	private String prefix = "";

	private MessageCodeFormatter formatter = DEFAULT_FORMATTER;


	/**
	 * Specify a prefix to be applied to any code built by this resolver.
	 * <p>Default is none. Specify, for example, "validation." to get
	 * error codes like "validation.typeMismatch.name".
	 */
	/**
	 *指定要应用于此解析程序生成的任何代码的前缀。
	 *<p>默认值为none。例如，指定“validation”以获得
	 *错误代码，如“validation.typeMismatch.name”。
	 */
	public void setPrefix(@Nullable String prefix) {
		this.prefix = (prefix != null ? prefix : "");
	}

	/**
	 * Return the prefix to be applied to any code built by this resolver.
	 * <p>Returns an empty String in case of no prefix.
	 */
	/**
	 *返回要应用于此解析程序生成的任何代码的前缀。
	 *＜p＞如果没有前缀，则返回一个空字符串。
	 */
	protected String getPrefix() {
		return this.prefix;
	}

	/**
	 * Specify the format for message codes built by this resolver.
	 * <p>The default is {@link Format#PREFIX_ERROR_CODE}.
	 * @since 3.2
	 * @see Format
	 */
	/**
	 *指定此解析程序生成的消息代码的格式。
	 *＜p＞默认值为｛@link Format#PREFIX_ERROR_CODE｝。
	 *@自3.2起
	 *@见格式
	 */
	public void setMessageCodeFormatter(@Nullable MessageCodeFormatter formatter) {
		this.formatter = (formatter != null ? formatter : DEFAULT_FORMATTER);
	}


	@Override
	public String[] resolveMessageCodes(String errorCode, String objectName) {
		return resolveMessageCodes(errorCode, objectName, "", null);
	}

	/**
	 * Build the code list for the given code and field: an
	 * object/field-specific code, a field-specific code, a plain error code.
	 * <p>Arrays, Lists and Maps are resolved both for specific elements and
	 * the whole collection.
	 * <p>See the {@link DefaultMessageCodesResolver class level javadoc} for
	 * details on the generated codes.
	 * @return the list of codes
	 */
	/**
	 *为给定的代码和字段生成代码列表：
	 *特定于对象/字段的代码、特定于字段的代码和纯错误代码。
	 *＜p＞对于特定元素和
	 *整个系列。
	 *<p>有关
	 *生成代码的详细信息。
	 *@返回代码列表
	 */
	@Override
	public String[] resolveMessageCodes(String errorCode, String objectName, String field, @Nullable Class<?> fieldType) {
		Set<String> codeList = new LinkedHashSet<>();
		List<String> fieldList = new ArrayList<>();
		buildFieldList(field, fieldList);
		addCodes(codeList, errorCode, objectName, fieldList);
		int dotIndex = field.lastIndexOf('.');
		if (dotIndex != -1) {
			buildFieldList(field.substring(dotIndex + 1), fieldList);
		}
		addCodes(codeList, errorCode, null, fieldList);
		if (fieldType != null) {
			addCode(codeList, errorCode, null, fieldType.getName());
		}
		addCode(codeList, errorCode, null, null);
		return StringUtils.toStringArray(codeList);
	}

	private void addCodes(Collection<String> codeList, String errorCode, @Nullable String objectName, Iterable<String> fields) {
		for (String field : fields) {
			addCode(codeList, errorCode, objectName, field);
		}
	}

	private void addCode(Collection<String> codeList, String errorCode, @Nullable String objectName, @Nullable String field) {
		codeList.add(postProcessMessageCode(this.formatter.format(errorCode, objectName, field)));
	}

	/**
	 * Add both keyed and non-keyed entries for the supplied {@code field}
	 * to the supplied field list.
	 */
	/**
	 *为提供的｛@code字段｝添加键控和非键控条目
	 *到提供的字段列表。
	 */
	protected void buildFieldList(String field, List<String> fieldList) {
		fieldList.add(field);
		String plainField = field;
		int keyIndex = plainField.lastIndexOf('[');
		while (keyIndex != -1) {
			int endKeyIndex = plainField.indexOf(']', keyIndex);
			if (endKeyIndex != -1) {
				plainField = plainField.substring(0, keyIndex) + plainField.substring(endKeyIndex + 1);
				fieldList.add(plainField);
				keyIndex = plainField.lastIndexOf('[');
			}
			else {
				keyIndex = -1;
			}
		}
	}

	/**
	 * Post-process the given message code, built by this resolver.
	 * <p>The default implementation applies the specified prefix, if any.
	 * @param code the message code as built by this resolver
	 * @return the final message code to be returned
	 * @see #setPrefix
	 */
	/**
	 *对由该解析程序生成的给定消息代码进行后处理。
	 *<p>默认实现应用指定的前缀（如果有的话）。
	 *@param code此解析程序生成的消息代码
	 *@return要返回的最终消息代码
	 *@参见#setPrefix
	 */
	protected String postProcessMessageCode(String code) {
		return getPrefix() + code;
	}


	/**
	 * Common message code formats.
	 * @see MessageCodeFormatter
	 * @see DefaultMessageCodesResolver#setMessageCodeFormatter(MessageCodeFormatter)
	 */
	/**
	 *常见的消息代码格式。
	 *@请参阅MessageCodeFormatter
	 *@请参阅DefaultMessageCodesResolver#setMessageCodeFormatter（MessageCodeFormater）
	 */
	public enum Format implements MessageCodeFormatter {

		/**
		 * Prefix the error code at the beginning of the generated message code. e.g.:
		 * {@code errorCode + "." + object name + "." + field}
		 */
		/**
		 *在生成的消息代码的开头加上错误代码的前缀。例如：
		 *｛@code errorCode+“.”+对象名称+“.“+字段｝
		 */
		PREFIX_ERROR_CODE {
			@Override
			public String format(String errorCode, @Nullable String objectName, @Nullable String field) {
				return toDelimitedString(errorCode, objectName, field);
			}
		},

		/**
		 * Postfix the error code at the end of the generated message code. e.g.:
		 * {@code object name + "." + field + "." + errorCode}
		 */
		/**
		 *将错误代码Postfix到生成的消息代码的末尾。例如：
		 *｛@code对象名称+“.”+字段+“.“+errorCode｝
		 */
		POSTFIX_ERROR_CODE {
			@Override
			public String format(String errorCode, @Nullable String objectName, @Nullable String field) {
				return toDelimitedString(objectName, field, errorCode);
			}
		};

		/**
		 * Concatenate the given elements, delimiting each with
		 * {@link DefaultMessageCodesResolver#CODE_SEPARATOR}, skipping zero-length or
		 * null elements altogether.
		 */
		/**
		 *连接给定的元素，用
		 *｛@link DefaultMessageCodesResolver#CODE_SEPARATOR｝，跳过零长度或
		 *空元素。
		 */
		public static String toDelimitedString(String... elements) {
			StringJoiner rtn = new StringJoiner(CODE_SEPARATOR);
			for (String element : elements) {
				if (StringUtils.hasLength(element)) {
					rtn.add(element);
				}
			}
			return rtn.toString();
		}
	}

}
